package me.zhengjie.log.aop;

import com.alibaba.fastjson2.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.log.dao.LogDao;
import me.zhengjie.log.utils.IpUtil;
import me.zhengjie.log.utils.RequestParamUtils;
import me.zhengjie.log.utils.Result;
import me.zhengjie.log.utils.SnowIdUtil;
import me.zhengjie.pojo.vo.AgvTaskSubmitVo;
import me.zhengjie.utils.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class WebLogAspect extends WebLogBase {

    private final WebLogAnnotationScanner webLogAnnotationScanner;

    private final IpUtil ipUtil;

    private final static String WCS = "WCS";
    private final static String MES = "MES";
    private final static String CHINA = "中国佛山";
    private final static String UNKNOWN_REGION = "未知地区";
    private final static String UNKNOWN_IP = "未知IP";
    private final static String UNKNOWN_USER = "未知用户";
    private final static String UNKNOWN_ADDRESS = "未知地址";
    private final static String FILE_PATH = "classpath:geoip/GeoLite2-City.mmdb";

    // 线程本地存储日志对象（每个方法调用一个独立实例）
    private static final ThreadLocal<Map<String, LogDao>> contextHolderMap = new ThreadLocal<>();
    // 全局调用链ID（整个请求共享）
    private static final ThreadLocal<String> TRACE_ID = new ThreadLocal<>();
    // 调用层级（嵌套深度）
    private static final ThreadLocal<Integer> CALL_LEVEL = new ThreadLocal<>();
    // 调用顺序计数器（整个请求内自增）
    private static final ThreadLocal<AtomicInteger> SEQUENCE = new ThreadLocal<>();
    // 请求开始时间（整个请求共享）
    private static final ThreadLocal<Map<String, Long>> contextTimeMap = new ThreadLocal<>();

    @Pointcut("@annotation(me.zhengjie.log.aop.WebLog)")
    public void webLog() {
    }

    /**
     * 环绕通知：统一控制调用链生命周期，计算方法耗时
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 初始化全局请求信息（仅首次进入请求时执行）
        initGlobalTraceInfo();

        // 初始化当前方法的LogDao（确保每个方法调用都有独立实例）
        initMethodLogDao(proceedingJoinPoint);

        LogDao webLogDao = this.findWebLogDao(proceedingJoinPoint);

        try {
            // 执行目标方法
            Object result = proceedingJoinPoint.proceed();
            return result;
        } catch (Throwable throwable) {
            // 异常时标记状态
            webLogDao.setStatus(500);
            webLogDao.setIsSuccess("失败");
            webLogDao.setIsReload(0);
            throw throwable;
        }
    }

    /**
     * 前置通知：记录方法开始信息，更新调用顺序和层级
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) {
        try {
            LogDao webLogDao = this.findWebLogDao(joinPoint);
            if (TRACE_ID.get() == null) {
                // 生成或获取全局traceId
                String traceId = this.getTraceIdFromHeader();
                TRACE_ID.set(traceId);
            }
            if (CALL_LEVEL.get() == null) {
                // 初始化调用层级（入口方法为1级）
                CALL_LEVEL.set(1);
            }
            if (SEQUENCE.get() == null) {
                // 初始化全局计数器和时间
                SEQUENCE.set(new AtomicInteger(0));
            }
            String traceId = TRACE_ID.get();
            int level = CALL_LEVEL.get();
            int sequence = SEQUENCE.get().incrementAndGet(); // 顺序自增

            // 补充LogDao的调用链信息
            webLogDao.setTraceId(traceId);
            webLogDao.setSequence(sequence);
            webLogDao.setCallLevel(level);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 后置返回通知：记录响应参数
     */
    @AfterReturning(pointcut = "webLog()", returning = "response", argNames = "joinPoint, response")
    public void doAfterReturning(JoinPoint joinPoint, Object response) {
        try {
            LogDao webLogDao = this.findWebLogDao(joinPoint);
            if (webLogDao == null) {
                return;
            }

            // 解析响应状态码
            int status = 200;
            if (response instanceof ResponseEntity) {
                status = ((ResponseEntity<?>) response).getStatusCode().value();
                status = String.valueOf(status).startsWith("2") ? 200 : 500;
            } else if (response instanceof Result) {
                status = ((Result<?>) response).getCode();
            }else if (response instanceof AgvTaskSubmitVo){
                String code = ((AgvTaskSubmitVo) response).getCode();
                if ("SUCCESS".equals(code)) {
                    status = 200;
                }else {
                    status = 500;
                }
            }
            webLogDao.setStatus(status);
            webLogDao.setResponseParams(JSON.toJSONString(response));
            if (status != 200){
                webLogDao.setIsSuccess("失败");
                webLogDao.setIsReload(0);
            }else {
                webLogDao.setIsSuccess("成功");
                webLogDao.setIsReload(1);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 异常通知：记录异常信息
     */
    @AfterThrowing(pointcut = "webLog()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        try {
            LogDao webLogDao = this.findWebLogDao(joinPoint);
            if (webLogDao == null) {
                return;
            }

            // 记录异常堆栈
            StringBuilder stackTrace = new StringBuilder();
            stackTrace.append("异常信息：").append(e.getMessage()).append("\n堆栈：");
            for (int i = 0; i < Math.min(30, e.getStackTrace().length); i++) {
                stackTrace.append(e.getStackTrace()[i]).append("\n");
            }
            webLogDao.setStackTrace(stackTrace.toString());
            webLogDao.setIsReload(0);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * 后置通知：统一存储日志，清理当前方法的ThreadLocal，控制调用层级
     */
    @After("webLog()")
    public void doAfter(JoinPoint joinPoint) {
        try {
            LogDao webLogDao = this.findWebLogDao(joinPoint);
            if (webLogDao == null) {
                return;
            }

            // 存储日志（仅当注解标记需要存储时）
            WebLogMetadata metadata = getAspectLogWebLogMetadata(joinPoint);
            if (metadata == null) {
                return;
            }
            if (metadata.isEnterData()) {
                this.setWebLogTime(joinPoint);
                String method = webLogDao.getMethod();
                String traceId = webLogDao.getTraceId();
                if (traceId == null){
                    webLogDao.setTraceId(String.valueOf(SnowIdUtil.nextId()));
                }
                if (method != null){
                    WebLogAnnotationScanner.addLog(webLogDao);
                }

            }
            this.printLog(joinPoint, webLogDao);
            if (CALL_LEVEL.get() != null) {
                int level = CALL_LEVEL.get();
                // 递减调用层级（为下一层嵌套调用做准备）
                CALL_LEVEL.set(level - 1);
                // 当回到入口层级（LEVEL=0）
                // 清理全局ThreadLocal（整个请求结束）
                if (level == 1) {
                    // 清理全局变量
                    clearGlobalTraceInfo();
                }
                // 清理当前方法的LogDao（避免嵌套调用冲突）
                this.removeWebLogDao(joinPoint);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // ------------------------------ 初始化与工具方法 ------------------------------

    // 初始化全局请求信息（整个请求共享，如traceId、开始时间）
    private void initGlobalTraceInfo() {
        if (TRACE_ID.get() == null) {
            // 生成或获取全局traceId
            String traceId = this.getTraceIdFromHeader();
            TRACE_ID.set(traceId);
            // 初始化全局计数器和时间
            SEQUENCE.set(new AtomicInteger(0));
            // 初始化调用层级（入口方法为1级）
            CALL_LEVEL.set(1);
        } else {
            // 嵌套调用时，层级+1
            CALL_LEVEL.set(CALL_LEVEL.get() + 1);
        }
    }

    // 初始化当前方法的LogDao（每个方法调用独立实例）
    private void initMethodLogDao(JoinPoint joinPoint) {
        if (contextHolderMap.get() == null) {
            contextHolderMap.set(new HashMap<>());
        }
        if (contextTimeMap.get() == null) {
            contextTimeMap.set(new HashMap<>());
        }

        try {
            WebLogMetadata metadata = getAspectLogWebLogMetadata(joinPoint);
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes != null ? attributes.getRequest() : null;
            // 构建LogDao基础信息
            LogDao logDao = new LogDao();
            logDao.setIsReload(1);
            logDao.setLogType(metadata.getCategory());
            logDao.setDescription(metadata.getDescription());
            logDao.setClazz(joinPoint.getSignature().getDeclaringTypeName());
            logDao.setMethod(joinPoint.getSignature().getName());
            logDao.setHttpMethod(request != null ? request.getMethod() : "");
            String ipAddress = ipUtil.getRemoteIp(request) == null ? UNKNOWN_IP : ipUtil.getRemoteIp();
            logDao.setRequestIp(ipAddress);
            logDao.setUsername(getUsername(metadata.getCategory()));
            logDao.setCreateTime(new Timestamp(System.currentTimeMillis()));
            logDao.setBrowser(request != null ? request.getRequestURL().toString() : UNKNOWN_ADDRESS);
            logDao.setAddress(request != null ? request.getRequestURL().toString() : UNKNOWN_ADDRESS);
            logDao.setRequestTime(new Timestamp(System.currentTimeMillis()));
            // 记录请求参数（仅记录非敏感参数）
            Object[] args = joinPoint.getArgs();
            logDao.setRequestParams(args != null && args.length > 0 ? JSON.toJSONString(args[0]) : "");
            logDao.setRequestParamsAll(RequestParamUtils.getAllParamsAsString(args, CALL_LEVEL));
            String methodUnionId = this.getMethodUnionId(joinPoint);
            contextHolderMap.get().put(methodUnionId, logDao);
            contextTimeMap.get().put(methodUnionId, System.currentTimeMillis());
        } catch (Exception e) {
            // 异常时仍创建空实例，避免后续空指针
            String methodUnionId = this.getMethodUnionId(joinPoint);
            contextHolderMap.get().put(methodUnionId, new LogDao());
            contextTimeMap.get().put(methodUnionId, System.currentTimeMillis());
        }
    }


    //  获取注解元数据
    public WebLogMetadata getAspectLogWebLogMetadata(JoinPoint joinPoint) {
        try {
            String methodUnionId = this.getMethodUnionId(joinPoint);
            return webLogAnnotationScanner.getWebLogMetadata(methodUnionId);
        } catch (Exception e) {
            // 返回默认空元数据
            return new WebLogMetadata();
        }
    }

    // 获取用户名（适配不同业务场景）
    private String getUsername(String category) {
        try {
            if (WCS.equals(category) || MES.equals(category)) {
                return category;
            }
            return SecurityUtils.getCurrentUsername();
        } catch (Exception e) {
            return UNKNOWN_USER;
        }
    }

    // 清理全局ThreadLocal（整个请求结束时）
    private void clearGlobalTraceInfo() {
        TRACE_ID.remove();
        CALL_LEVEL.remove();
        SEQUENCE.remove();
    }

    public void updateWebLogDao(JoinPoint joinPoint, LogDao logDao) {
        String methodUnionId = this.getMethodUnionId(joinPoint);
        if (contextHolderMap.get().containsKey(methodUnionId)) {
            contextHolderMap.get().put(methodUnionId, logDao);
        }
    }

    public LogDao findWebLogDao(JoinPoint joinPoint) {
        String methodUnionId = this.getMethodUnionId(joinPoint);
        if (contextHolderMap.get().containsKey(methodUnionId)) {
            return contextHolderMap.get().get(methodUnionId);
        }
        return new LogDao();
    }

    public void removeWebLogDao(JoinPoint joinPoint) {
        String methodUnionId = this.getMethodUnionId(joinPoint);
        contextHolderMap.get().remove(methodUnionId);
        contextTimeMap.get().remove(methodUnionId);
    }

    public void setWebLogTime(JoinPoint joinPoint) {
        String methodUnionId = this.getMethodUnionId(joinPoint);
        if (!contextTimeMap.get().containsKey(methodUnionId)) {
            return;
        }
        if (!contextHolderMap.get().containsKey(methodUnionId)) {
            return;
        }
        Long start = contextTimeMap.get().get(methodUnionId);
        LogDao logDao = contextHolderMap.get().get(methodUnionId);
        long end = System.currentTimeMillis();
        logDao.setExecTime(end - start);

    }

    public void printLog(JoinPoint joinPoint, LogDao logDao) {
        try {
            WebLogMetadata logMetadata = this.getAspectLogWebLogMetadata(joinPoint);
            if (logMetadata == null || logMetadata.getLogName() == null) {
                return;
            }
            Logger webLogPrint = LoggerFactory.getLogger(logMetadata.getLogName());
            if (logDao.getStatus() == 200) {
                logDao.setIsSuccess("成功");
                webLogPrint.info("响应成功:{}", logDao);
            } else {
                webLogPrint.error("响应失败:{}", logDao);
                logDao.setIsSuccess("失败");
            }
        } catch (NullPointerException e) {
            // 记录异常日志，不影响主流程
            log.error("日志记录失败，原因：{}，日志数据：{}", e.getMessage(), logDao);
        }
    }
}